/*
 * Copyright:  2018 smarabbit studio.
 *
 *  Licensed under the Confluent Community License; you may not use this file
 *  except in compliance with the License.  You may obtain a copy of the License at
 *
 *  http://www.confluent.io/confluent-community-license
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 *  WARRANTIES OF ANY KIND, either express or implied.  See the License for the
 *  specific language governing permissions and limitations under the License.
 *
 *  @作   者： 黄开晖 (117227773@qq.com)
 *  @日   期:  2019年1月7日
 */

package com.massyframework.beanskin.spring.beancontext.provider;

import java.io.IOException;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;

import javax.servlet.Servlet;
import javax.servlet.ServletConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;

import org.springframework.web.context.support.AbstractRefreshableWebApplicationContext;
import org.springframework.web.servlet.DispatcherServlet;

import com.massyframework.beanskin.assembly.Assembly;
import com.massyframework.beanskin.assembly.AssemblyContextDelegate;
import com.massyframework.beanskin.assembly.handling.ActivationHandler;
import com.massyframework.beanskin.assembly.handling.EnvironmentHandler;
import com.massyframework.beanskin.assembly.handling.HandlerRegistration;
import com.massyframework.beanskin.assembly.handling.ReadyingException;
import com.massyframework.beanskin.assembly.handling.ReadyingHandler;
import com.massyframework.beanskin.service.ServiceReference;
import com.massyframework.beanskin.service.ServiceRegistration;
import com.massyframework.beanskin.service.ServiceRegistry;
import com.massyframework.beanskin.service.ServiceRepository;
import com.massyframework.beanskin.spring.beancontext.DelegeteServletContext;
import com.massyframework.beanskin.spring.beancontext.DependencyServiceBeanDefinitionRegistryPostProcessor;
import com.massyframework.beanskin.spring.beancontext.DependencyServiceInitializer;
import com.massyframework.beanskin.spring.beancontext.SpringBeanContextAdapter;
import com.massyframework.beanskin.spring.beancontext.WebApplicationContextFactory;
import com.massyframework.beanskin.web.utils.ServletRegistrationUtils;

/**
 * 提供DispatcherServlet的装配件上下文处理
 */
final class SpringServletHandler extends DependencyServiceBeanDefinitionRegistryPostProcessor
		implements EnvironmentHandler, ReadyingHandler, ActivationHandler {

	private volatile boolean runAtJ2EE = true;
	private volatile ServiceRegistration<Servlet> servletRegistration;
	private volatile HandlerRegistration<AssemblyContextDelegate<SpringBeanContextAdapter>> contextRegistration;

	private volatile ServletConfig config;
	private volatile DispatcherServlet servlet;

	/**
	 * 构造方法
	 */
	public SpringServletHandler() {
	}

	/**
	 * 初始化
	 *
	 * @throws Exception 发生非预期的例外
	 */
	@Override
	protected void init() throws Exception {
		super.init();
		final Assembly assembly = this.getAssociatedAssembly();
		ServiceRepository repository = this.getServiceRepository(assembly);

		Optional<ServletContext> optional = repository.findService(ServletContext.class);
		ServletException ex = optional.map(context -> {
			this.runAtJ2EE = true;
			try {
				ServletRegistrationUtils.createServletRegistration(context, assembly, null, "true");
			} catch (ServletException e) {
				return e;
			}
			return null;
		}).orElse(null);

		if (ex != null) {
			throw ex;
		}
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see com.massyframework.assembly.spi.ReadyingHandler#readying()
	 */
	@Override
	public synchronized void readying() throws ReadyingException {
		if (!this.runAtJ2EE) {
			return;
		}

		this.exportService();
	}

	/**
	 * 输出服务
	 */
	protected synchronized void exportService() {
		if (this.servletRegistration == null) {
			Assembly assembly = this.getAssociatedAssembly();
			ServiceRepository repository = this.getServiceRepository(assembly);
			ServiceRegistry registry = repository.getServiceRegistry();

			Bridge servlet = new Bridge();
			Map<String, Object> props = new HashMap<>();
			props.put(ServiceReference.SERVICE_NAME, assembly.getConfig().getParameter("servlet.name"));

			this.servletRegistration = registry.register(Servlet.class, servlet, props);
		}
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see com.massyframework.assembly.spi.ReadyingHandler#unreadyed()
	 */
	@Override
	public void unreadyed() throws ReadyingException {
		this.unexportService();
	}

	/**
	 * 撤销服务输出
	 */
	protected synchronized void unexportService() {
		if (this.servletRegistration != null) {
			this.servletRegistration.unregister();
			this.servletRegistration = null;
		}
	}

	/**
	 * 在Web场景下注册的组件是否已准备就绪？<br>
	 * Servlet的实例化要视注册时的选项，如果是首次请求时才实例化，那在发起首次请求前，本方法只能返回<code>false</code>
	 *
	 * @return {@link boolean},返回<code>true</code>表示已就绪，否则返回<code>false</code>
	 */
	@Override
	public boolean isReady() {
		if (!this.runAtJ2EE) {
			return false;
		}

		return this.config != null;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see com.massyframework.assembly.spi.ActivationHandler#doStarting()
	 */
	@Override
	public synchronized void doStarting() throws Exception {
		if (this.contextRegistration == null) {

			Assembly assembly = this.getAssociatedAssembly();
			try {
				WebApplicationContextFactory factory = new WebApplicationContextFactory();
				AbstractRefreshableWebApplicationContext context = factory.createApplicationContext(assembly,
						new DependencyServiceInitializer<AbstractRefreshableWebApplicationContext>(this));
				SpringBeanContextAdapter adapter = new SpringBeanContextAdapter(context);
				AssemblyContextDelegate<SpringBeanContextAdapter> delegate = new AssemblyContextDelegate<SpringBeanContextAdapter>(
						adapter);
				delegate.init(this.getLifecycleManager());
				this.setAssemblyContext(delegate);

				this.servlet = new DispatcherServlet(context);
				servlet.init(this.config);

				this.contextRegistration = this.getLifecycleManager().register(delegate);
			} catch (Exception e) {
				throw e;
			}
		}
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see com.massyframework.assembly.spi.ActivationHandler#doStopped()
	 */
	@Override
	public synchronized void doStopped() throws Exception {
		if (this.contextRegistration != null) {
			AssemblyContextDelegate<SpringBeanContextAdapter> context = this.contextRegistration.getHandler();
			this.contextRegistration.unregister();
			this.contextRegistration = null;

			try {
				context.getBeanContext().getApplicationContext().close();
			} finally {
				this.servlet.destroy();
				this.setAssemblyContext(null);
				this.servlet = null;
			}
		}
	}

	protected void doService(ServletRequest req, ServletResponse res) throws ServletException, IOException {
		Servlet dispatcherServlet = this.servlet;
		if (dispatcherServlet != null) {
			dispatcherServlet.service(req, res);
		}
	}

	/**
	 * 根据<code>assembly</code>获取{@link ServiceRepository}
	 * 
	 * @param assembly {@link Assembly}, 装配件
	 * @return {@link ServiceRepository}
	 */
	protected ServiceRepository getServiceRepository(Assembly assembly) {
		return ServiceRepository.retrieveFrom(assembly);
	}

	/**
	 * ServletBridge在DelegateServlet和DispatcherServletEx之间的连接桥
	 */
	private class Bridge implements Servlet {

		@Override
		public void init(ServletConfig config) throws ServletException {
			SpringServletHandler.this.config = new DelegateConfig(config);
			try {
				SpringServletHandler.this.getLifecycleManager().start();
			} catch (Exception e) {
				logError(e.getMessage(), e);
				throw new ServletException(e.getMessage(), e);
			}
		}

		@Override
		public ServletConfig getServletConfig() {
			return config;
		}

		@Override
		public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
			doService(req, res);
		}

		@Override
		public String getServletInfo() {
			return null;
		}

		@Override
		public void destroy() {
			try {
				SpringServletHandler.this.getLifecycleManager().stop();
			} catch (Exception e) {
				logError(e.getMessage(), e);
			}
		}

	}
	
	private class DelegateConfig implements ServletConfig {
		private ServletConfig config;
		private ServletContext context;
		
		public DelegateConfig(ServletConfig config) {
			this.config = config;
			this.context = new DelegeteServletContext(config.getServletContext(), getAssociatedAssembly());
		}

		@Override
		public String getServletName() {
			return this.config.getServletName();
		}

		@Override
		public ServletContext getServletContext() {
			return this.context;
		}

		@Override
		public String getInitParameter(String name) {
			return this.config.getInitParameter(name);
		}

		@Override
		public Enumeration<String> getInitParameterNames() {
			return this.config.getInitParameterNames();
		}
		
	}
}
